package org.merak.core;

import java.io.File;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import org.apache.log4j.Logger;
import org.merak.core.model.IShutdownHook;
import org.merak.core.model.ProgrammaticException;
import org.merak.core.model.SettingException;
import org.merak.core.persistence.jdbc.DatabaseSystem;
import org.merak.core.persistence.jdbc.XConnection;
import org.merak.core.persistence.jdbc.settings.ConnectionDetail;
import org.merak.core.persistence.jdbc.settings.ConnectionSetting;
import org.merak.core.persistence.jdbc.settings.SettingLoader;
import org.merak.core.persistence.jdbc.settings.SettingType;

public final class Database implements IShutdownHook {

	//~ Attributes ///////////////////////////////////////////////////////////////
	//****************************************************************************
	private String                  configFile  = "Databases.xml";
	private Map<String,XConnection> connections = null;
	private XConnection             standard    = null;
	private Logger                  logger      = Logger.getLogger("Database");

	//~ Constructors /////////////////////////////////////////////////////////////
	//****************************************************************************
	protected Database() {

		try {
			// Initialize Attributes
			this.connections = Collections.synchronizedMap(
				new HashMap<String,XConnection>()
			);
			// Initialize Configurations
			this.init();
			// Install a shutdown notifier
			Application.addShutdownHook(this);
		}
		catch (Exception e) {
			// Make sure you log the exception, as it might be swallowed
			this.logger.fatal("Error while trying to initialize Database",e);
			throw new ExceptionInInitializerError(e);
		}

	}

	//~ Destructor ///////////////////////////////////////////////////////////////
	//****************************************************************************
	@Override
	public void shutdown() {

		// Close all opened connections
		for (XConnection conn : this.connections.values()) try {
			if (conn.isClosed()) continue;
			this.logger.info("Closing connections to '" + conn.getSettings() + "'");
			conn.close();
		}
		catch (Exception e) {
			// Make sure you log the exception, as it might be swallowed
			this.logger.fatal("Error while trying to close Database Connection",e);
			throw new RuntimeException(e);
		}
		
		// Close all JDBC drivers
//		Enumeration<Driver> drivers =  DriverManager.getDrivers();
//		while (drivers.hasMoreElements()) try {
//			Driver d = drivers.nextElement();
//			this.logger.info("Closing driver '" + d + "'");
//			DriverManager.deregisterDriver( d );			
//		}
//		catch (Exception e) {
//			// Make sure you log the exception, as it might be swallowed
//			this.logger.fatal("Error while trying to unregister Database Driver",e);
//			throw new RuntimeException(e);
//		}		
		
		// Clear the connection map
		this.connections.clear();

	}

	//~ Methods //////////////////////////////////////////////////////////////////
	//****************************************************************************
	/** This method is able to initialize or restart the DatabaseManager, reading
	 * configurations from ApplicationData/Database.xml
	 * @throws Exception */
	private void init() throws Exception {

		// Auxiliary
		File        input = Application.paths.applicationData.getFile(this.configFile);
		XConnection conn  = null;
		int         index = 0;

		// First, close open connections
		this.shutdown();

		// Load configurations for each database
		for (ConnectionSetting settings : SettingLoader.load(input)) try {
			// Skip not wanted connections
			settings.setIndex(index++);
			if (settings.isSkip()) continue;

			// Prepare and Validate settings
			SettingLoader.preprocess(settings);
			settings.validate();

			// The database Id must be unique
			boolean test = this.connections.containsKey(settings.getId());
			if ( test ) throw new SettingException(
				"Database Id is duplicated, must be unique."
			);

			/* Prepare a connection that will be lazily initialized based on
			 * provided database settings */
			conn = new XConnection(settings);

			// If it is the first not skipped database, set as default one
			if ( this.connections.isEmpty() ) this.standard = conn;

			// Register the database connection at connection map
			this.connections.put(settings.getId(),conn);
		}
		catch (SettingException ex) { this.logger.error(
			"Ignoring '" + settings + "': " + ex.getMessage()
		);}

	}
	//****************************************************************************
	public Logger getLogger() {
		return this.logger;
	}
	//****************************************************************************
	/** @returns The Primary Connection */
	public XConnection getConnection() throws Exception {

		XConnection conn = this.standard;
		if ( !conn.getSettings().isKeep() ) {
			conn = new XConnection( conn.getSettings() );
		}
		return conn;

	}
	//****************************************************************************
	public XConnection getConnection(String name) {

		// Find connection by name. Connection must exist
		XConnection conn = this.connections.get(name);
		if ( conn == null  ) throw new ProgrammaticException (
				"There is no connection named as " + name
		);
		if ( !conn.getSettings().isKeep() ) {
			conn = new XConnection( conn.getSettings() );
		}
		return conn;

	}
	//****************************************************************************
	/** @returns The Primary Connection */
	public Collection<XConnection> getConnections() throws Exception {
		return this.connections.values();
	}
	//****************************************************************************
	/** Create an on-the-fly connection to the MsAccess file */
	public XConnection connectToMsAccess(File file) throws Exception {

		ConnectionSetting settings = new ConnectionSetting();
		ConnectionDetail  detail   = settings.getDetail();
		DatabaseSystem    system   = DatabaseSystem.getInstance("MsAccess");

		settings.setType(SettingType.FORM);
		detail.setSystem("MsAccess");
		detail.setDriver(system.getStandardDriver());
		detail.setDriverType("Microsoft Access Driver (*.mdb)");
		detail.setDatabase(file.getAbsolutePath());

		return new XConnection(settings);

	}
	//****************************************************************************

}
